/*
 * x86_64/lapic.hh - Local APIC data types and functions.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#ifndef HEADER_APIC_LAPIC_HH
#define HEADER_APIC_LAPIC_HH

#include HEADER (utils,setters.hh)

START_NAMESPACE (x86_64)

template <u64_t BASE>
class lapic
{
public:
  enum offset_t
  {
    OFFSET_ID = 0x020,
    OFFSET_VERSION = 0x030,
    OFFSET_TPR = 0x080,
    OFFSET_APR = 0x090,
    OFFSET_PPR = 0x0A0,
    OFFSET_EOI = 0x0B0,
    OFFSET_LDR = 0x0D0,
    OFFSET_DFR = 0x0E0,
    OFFSET_SIV = 0x0F0,
    OFFSET_ISR = 0x100,
    OFFSET_TMR = 0x180,
    OFFSET_IRR = 0x200,
    OFFSET_ESR = 0x280,
    OFFSET_ICR_LOW = 0x300,
    OFFSET_ICR_HIGH = 0x310,
    OFFSET_TIMER_LVTE = 0x320,
    OFFSET_PERF_CNTR_LVTE = 0x340,
    OFFSET_LINT0_LVTE = 0x350,
    OFFSET_LINT1_LVTE = 0x360,
    OFFSET_ERROR_LVTE = 0x370,
    OFFSET_TIMER_ICR = 0x380,
    OFFSET_TIMER_CCR = 0x390,
    OFFSET_TIMER_DCR = 0x3E0
  };

  enum dst_mode_t
  {
    DM_FIXED = 0,
    DM_LOWEST_PRI = 1,
    DM_SMI = 2,
    DM_NMI = 4,
    DM_INIT = 5,
    DM_STARTUP = 6,
    DM_EXT_INT = 7
  };

  enum model_t
  {
    MODEL_CLUSTER = 0x00,
    MODEL_FLAT = 0x0f
  };

  enum dst_t
  {
    DST_DST = 0,
    DST_SELF = 1,
    DST_ALL = 2,
    DST_NOT_SELF = 3
  };

  enum trig_mode_t
  {
    TRIG_MODE_EDGE = 0,
    TRIG_MODE_LEVEL = 1
  };

  enum timer_div_t
  {
    TIMER_DIV_1 = 0x0B,
    TIMER_DIV_2 = 0x00,
    TIMER_DIV_4 = 0x01,
    TIMER_DIV_8 = 0x02,
    TIMER_DIV_16 = 0x03,
    TIMER_DIV_32 = 0x08,
    TIMER_DIV_64 = 0x09,
    TIMER_DIV_128 = 0x0A
  };

  union icr_t
  {
    struct
      {
        u32_t vector : 8;
        u32_t DM : 3;
        u32_t DSTM : 1;
        u32_t DS : 1;
        u32_t : 1;
        u32_t LVL : 1;
        u32_t TM : 1;
        u32_t : 2;
        u32_t DSTS : 2;
        u32_t : 12;
      };
    u32_t raw;

    UTILS_RAW_ZERO (icr_t, u32_t)

    UTILS_SETTER (icr_t, u8_t, vector)
    UTILS_SETTER (icr_t, dst_mode_t, DM)
    UTILS_BOOL_SETTER (icr_t, DSTM)
    UTILS_BOOL_SETTER (icr_t, DS)
    UTILS_BOOL_SETTER (icr_t, LVL)
    UTILS_SETTER (icr_t, trig_mode_t, TM)
    UTILS_SETTER (icr_t, dst_t, DSTS)
  };

  union siv_t
  {
    struct
      {
        u32_t vector : 8;
        u32_t E : 1;
        u32_t FPC : 1;
        u32_t : 22;
      };
    u32_t raw;

    UTILS_RAW_ZERO (siv_t, u32_t)

    UTILS_SETTER (siv_t, u8_t, vector)
    UTILS_BOOL_SETTER (siv_t, E)
    UTILS_BOOL_SETTER (siv_t, FPC)
  };

  union es_t
  {
    struct
      {
        u32_t SCS : 1;
        u32_t RCS : 1;
        u32_t SAE : 1;
        u32_t RAE : 1;
        u32_t : 1;
        u32_t SIV : 1;
        u32_t RIV : 1;
        u32_t IRA : 1;
        u32_t : 24;
      };
    u32_t raw;

    UTILS_RAW_ZERO (es_t, u32_t)

    UTILS_BOOL_SETTER (es_t, SCS)
    UTILS_BOOL_SETTER (es_t, RCS)
    UTILS_BOOL_SETTER (es_t, SAE)
    UTILS_BOOL_SETTER (es_t, RAE)
    UTILS_BOOL_SETTER (es_t, SIV)
    UTILS_BOOL_SETTER (es_t, RIV)
    UTILS_BOOL_SETTER (es_t, IRA)
  };

  union lvte_t
  {
    struct
      {
        u32_t vector : 8;
        u32_t DM : 3;
        u32_t : 1;
        u32_t DS : 1;
        u32_t IPP : 1;
        u32_t IRR : 1;
        u32_t TM : 1;
        u32_t M : 1;
        u32_t P : 1;
        u32_t : 14;
      };
    u32_t raw;

    UTILS_RAW_ZERO (lvte_t, u32_t)

    UTILS_SETTER (lvte_t, u8_t, vector)
    UTILS_SETTER (lvte_t, dst_mode_t, DM)
    UTILS_BOOL_SETTER (lvte_t, DS)
    UTILS_BOOL_SETTER (lvte_t, IPP)
    UTILS_BOOL_SETTER (lvte_t, IRR)
    UTILS_SETTER (lvte_t, trig_mode_t, TM)
    UTILS_BOOL_SETTER (lvte_t, M)
    UTILS_BOOL_SETTER (lvte_t, P)
  };

private:
  INLINE
  static volatile u32_t &
  reg (offset_t offset)
  {
    return *((volatile u32_t *) (BASE + offset));
  }

public:
#define __X86_64_LAPIC_GETTER_SETTER(REG,TYPE,NAME) \
  INLINE \
  static TYPE \
  get_ ## NAME () \
  { \
    return reg (OFFSET_ ## REG); \
  } \
  \
  INLINE \
  static void \
  set_ ## NAME (TYPE x) \
  { \
    reg (OFFSET_ ## REG) = x; \
  }

  INLINE
  static u8_t
  get_id ()
  {
    return (reg (OFFSET_ID) >> 24) & 0x0F;
  }

  INLINE
  static void
  set_id (u8_t id)
  {
    reg (OFFSET_ID) = (u32_t) id << 24;
  }

  INLINE
  static model_t
  get_model ()
  {
    return reg (OFFSET_DFR) >> 28;
  }

  INLINE
  static void
  set_model (model_t model)
  {
    reg (OFFSET_DFR) = (u32_t) model << 28 | ~0 >> 4;
  }

  __X86_64_LAPIC_GETTER_SETTER (SIV, siv_t, siv)
  __X86_64_LAPIC_GETTER_SETTER (ESR, es_t, es)

  INLINE
  static bool
  isr_p (u8_t i)
  {
    return (reg ((offset_t) (OFFSET_ISR + ((i & ~0x1F) << 3)))
            >> (i & 0x1F)) & 1;
  }

  INLINE
  static bool
  tmr_p (u8_t i)
  {
    return (reg ((offset_t) (OFFSET_TMR + ((i & ~0x1F) << 3)))
            >> (i & 0x1F)) & 1;
  }

  INLINE
  static bool
  irr_p (u8_t i)
  {
    return (reg ((offset_t) (OFFSET_IRR + ((i & ~0x1F) << 3)))
            >> (i & 0x1F)) & 1;
  }

  INLINE
  static bool
  icr_busy_p ()
  {
    icr_t icr = reg (OFFSET_ICR_LOW);
    return icr.DS == 1;
  }

  INLINE
  static void
  send (u8_t id, icr_t icr)
  {
    while (icr_busy_p ())
      ;

    reg (OFFSET_ICR_HIGH) = (u32_t) id << 24;
    reg (OFFSET_ICR_LOW) = icr.raw;
  }

  INLINE
  static void
  send (icr_t icr)
  {
    send (0, icr.set_DM (DM_FIXED).set_DSTS (DST_SELF));
  }

  __X86_64_LAPIC_GETTER_SETTER (TIMER_LVTE, lvte_t, timer_lvte)
  __X86_64_LAPIC_GETTER_SETTER (PERF_CNTR_LVTE, lvte_t, perf_cntr_lvte)
  __X86_64_LAPIC_GETTER_SETTER (LINT0_LVTE, lvte_t, lint0_lvte)
  __X86_64_LAPIC_GETTER_SETTER (LINT1_LVTE, lvte_t, lint1_lvte)
  __X86_64_LAPIC_GETTER_SETTER (ERROR_LVTE, lvte_t, error_lvte)

  __X86_64_LAPIC_GETTER_SETTER (TIMER_ICR, u32_t, timer_icr)
  __X86_64_LAPIC_GETTER_SETTER (TIMER_CCR, u32_t, timer_ccr)
  __X86_64_LAPIC_GETTER_SETTER (TIMER_DCR, timer_div_t, timer_dcr)

  INLINE
  static void
  init (u8_t id, bool assert_p)
  {
    icr_t icr;
    icr.set_DM (DM_INIT).set_TM (TRIG_MODE_LEVEL).set_LVL (assert_p);
    send (id, icr);
  }

  INLINE
  static void
  startup (u8_t id, u64_t addr)
  {
    icr_t icr;
    icr.set_DM (DM_STARTUP).set_LVL ().set_vector (addr >> 12);
    send (id, icr);
  }

  INLINE
  static void
  eoi ()
  {
    reg (OFFSET_EOI) = 0;
  }

#undef __X86_64_LAPIC_GETTER_SETTER
};

END_NAMESPACE

#endif /* HEADER_APIC_LAPIC_HH */

